package com.leonardo.beanme.metadatahandlers;

import java.io.InputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import com.leonardo.beanme.exceptions.XmlReadingException;
import com.leonardo.beanme.metadatahandlers.tags.BeanTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.FieldTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.FieldTypeTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.FormsConfigTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.GetterTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.InnerFieldTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.LabelTagHandler;
import com.leonardo.beanme.metadatahandlers.tags.SetterTagHandler;

public class BeanXmlReader extends BeanMetadataReader {
	
	private static final String source = "/META-INF/FormConf.xml"; 	
	private static final Class[] allDefaultHandlers = 
		{BeanTagHandler.class, FieldTagHandler.class, LabelTagHandler.class,	FieldTypeTagHandler.class, 
		GetterTagHandler.class, SetterTagHandler.class, FormsConfigTagHandler.class, NewTagTagHandler.class};
	
	private int nBeans = 0;
	private Vector beans = new Vector();
	
	public BeanXmlReader() throws Exception {
		SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
		InputStream inputStream = BeanXmlReader.class.getResourceAsStream(source);
		saxParser.parse(inputStream, new FormHandler());
	}
		
	public void reset() {
		nBeans = 0;
	}
	
	public boolean hasMoreBeans() {
		return nBeans < beans.size();
	}
	
	public FormMetadataContainer next() {
		return (FormMetadataContainer) beans.elementAt(nBeans++);
	}
	
	public Hashtable getMetadataContainers() {
		Hashtable customConfigs = new Hashtable();
		
		System.out.println("Putting beans in configuration");
		reset();
		while(hasMoreBeans()) {
			FormMetadataContainer bean = next();
			System.out.println("	Bean: " + bean.getId());
			customConfigs.put(bean.getId(), bean);
		}
		return customConfigs;
	}
	
	public class FormHandler extends DefaultHandler {
		private final String DOCUMENT_TAG = "forms-config";
		
		private FormMetadataContainer beanInfo;
		private FieldMetadataContainer fieldInfo;
		private Stack tagNameStack = new Stack();
		private Stack handlerStack = new Stack();
		
		private Hashtable handlers = new Hashtable(allDefaultHandlers.length);
		
		public FormHandler() {
			for(int i = 0; i < allDefaultHandlers.length; i++)
				try {
					TagHandler handler = (TagHandler) allDefaultHandlers[i].newInstance();
					handlers.put(handler.getTagName(), allDefaultHandlers[i]);
				} catch(Exception ex) {
					//never happens
					ex.printStackTrace();
				}
		}
		
		private TagHandler getTagHandler(String tagName) {
			try {
				TagHandler handler = (TagHandler) (((Class)handlers.get(tagName)).newInstance());
				handler.setOwner(this);
				return handler;
			} catch (Exception e) {
				// never happens
				e.printStackTrace();
			} 
			return null;
		}
		
		public void startDocument() {
			System.out.println("Start document.");
		}
		public void endDocument() {
			if(!tagNameStack.isEmpty())
				System.out.println("[WARN] Peek tag: " + (String) tagNameStack.peek());
			System.out.println("End document.");
		}

		public void startElement(String uri, String localName, String tagName, Attributes attributes) {
			startDebug(tagName, attributes);
			startTagValidation(tagName);
			tagNameStack.push(tagName);
	
			TagHandler nextHandler = getTagHandler(tagName);
			handlerStack.push(nextHandler);
			if(nextHandler instanceof InnerFieldTagHandler)
				checkInsideFieldTag();
			nextHandler.startTag(attributes);
		}
		
		
		public void characters(char[] ch, int start, int lenght) {
			TagHandler handler = (TagHandler) handlerStack.peek();	
			handler.characters(ch, start, lenght);
		}
		
		public void endElement(String uri, String localName, String tagName) {
			endDebug(tagName);
			endTagValidation(tagName);
			tagNameStack.pop();
			((TagHandler) handlerStack.pop()).endTag(); 
		}
		
		public void fatalError(SAXParseException ex) {
			throw new XmlReadingException("Error reading xml. Check if your not closing a tag " +
					"without open it before." + ex.getMessage());
		}
		
		private void startTagValidation(String tagName) {
			System.out.println("init start validation");
			if(!DOCUMENT_TAG.equals(tagName) && !checkDocumentOpened())
				throw new XmlReadingException("Missing " + DOCUMENT_TAG + " tag");
			if(!handlers.containsKey(tagName))
				throw new XmlReadingException("Found invalid tag " + tagName);
		}
		
		private void endTagValidation(String tagName) {
			if(!checkDocumentOpened() || !tagName.equals(tagNameStack.peek()))
				throw new XmlReadingException("Invalid closing tag " + tagName);
		}
		
		private boolean checkDocumentOpened() {
			return tagNameStack.contains(DOCUMENT_TAG);
		}
		
		private void checkInsideFieldTag() {
			if(!tagNameStack.contains("field"))
				throw new XmlReadingException("Incorrec place of tag field");
		}
		
		public FieldMetadataContainer getFieldInfo() {
			return fieldInfo;
		}
		
		public void setFieldInfo(FieldMetadataContainer fieldInfo) {
			this.fieldInfo = fieldInfo;
		}
		
		public FormMetadataContainer getBeanInfo() {
			return beanInfo;
		}
		
		public void setBeanInfo(FormMetadataContainer beanInfo) {
			this.beanInfo = beanInfo;
		}
		
		private void startDebug(String tagName, Attributes attributes) {
			System.out.println();
			System.out.println("Start tag: " + tagName);
			for(int i = 0; i < attributes.getLength(); i++) {
				System.out.println("	Attributes");
				System.out.println("	QName: " + attributes.getQName(i));
				System.out.println("	Value: " + attributes.getValue(i));
			}
		}
		
		private void endDebug(String tagName) {
			System.out.println();
			System.out.println("Closing tag: " + tagName);
		}
		
		public void addBean() {
			System.out.println("Adding bean " + beanInfo.getId());
			beanInfo.reset();
			while(beanInfo.hasMoreFields()) {
				System.out.println("Next Field");
				Enumeration it = beanInfo.next().getHandlers();
				while(it.hasMoreElements()) {
					TagHandler next = (TagHandler) it.nextElement();
					System.out.println("Tag: " + next.getTagName());
					if(next instanceof InnerFieldTagHandler)
						System.out.println("and Content: " + ((InnerFieldTagHandler) next).getInnerText());
				}
			}
			BeanXmlReader.this.beans.addElement(beanInfo);
		}
		
		public void addTagHandler(Class tagHandlerClass) throws InstantiationException, IllegalAccessException {
			TagHandler handler = (TagHandler) tagHandlerClass.newInstance();
			handlers.put(handler.getTagName(), tagHandlerClass);
		}
	}
}
